package panda.transaction;

import panda.buffer.Buffer;
import panda.buffer.PageFormatter;
import panda.file.Block;
import panda.index.bplustree.FileHeader;
import panda.server.Panda;
import panda.transaction.concurrency.ConcurrencyManager;
import panda.transaction.recovery.RecoveryManager;

public class Transaction {
	private int transactionNumber;

	private RecoveryManager recoveryManager;

	private ConcurrencyManager concurrencyManager;

	private BufferList bufferList = new BufferList();

	private static int nextTransactionNumber = 0;

	public Transaction() {
		transactionNumber = getTransactionNumber();
		recoveryManager = new RecoveryManager(transactionNumber);
		concurrencyManager   = new ConcurrencyManager();
	}

	public void memCpy(Block block , int fromPosition,int toPosition,int length){
		concurrencyManager.exclusiveLock(block);
		Buffer buffer = bufferList.getBuffer(block);
		int lsn = recoveryManager.setArray(buffer, toPosition,length);
		buffer.memCpy(fromPosition, toPosition, length, transactionNumber, lsn);
	}
	
	public void setInt(Block block, int offset, int value) {
		concurrencyManager.exclusiveLock(block);
		Buffer buffer = bufferList.getBuffer(block);
		int lsn = recoveryManager.setInt(buffer, offset);
		buffer.putInt(offset, value, transactionNumber, lsn);
		
	}

	public void setString(Block block, int offset, String value) {
		concurrencyManager.exclusiveLock(block);
		Buffer buffer = bufferList.getBuffer(block);
		int lsn = recoveryManager.setString(buffer, offset);
		buffer.putString(offset, value, transactionNumber, lsn);
	}

	public void setFloat(Block block, int offset, Float value) {
		concurrencyManager.exclusiveLock(block);
		Buffer buffer = bufferList.getBuffer(block);
		int lsn = recoveryManager.setFloat(buffer, offset);
		buffer.putFloat(offset, value, transactionNumber, lsn);
	}
	
	public void setLong(Block block, int offset, long value) {
		concurrencyManager.exclusiveLock(block);
		Buffer buffer = bufferList.getBuffer(block);
		int lsn = recoveryManager.setLong(buffer, offset);
		buffer.putLong(offset, value, transactionNumber, lsn);
	}
	public void setArray(Block block, int offset, byte[] value) {
		concurrencyManager.exclusiveLock(block);
		Buffer buffer = bufferList.getBuffer(block);
		int lsn = recoveryManager.setArray(buffer, offset);
		buffer.putArray(offset, value, transactionNumber, lsn);
	}
	
	public int getInt(Block block, int offset) {
		concurrencyManager.sharedLock(block);
		Buffer buf = bufferList.getBuffer(block);
		return buf.getInt(offset);
	}

	public String getString(Block block, int offset) {
		concurrencyManager.sharedLock(block);
		Buffer buf = bufferList.getBuffer(block);
		return buf.getString(offset);
	}

	public float getFloat(Block block, int offset) {
		concurrencyManager.sharedLock(block);
		Buffer buf = bufferList.getBuffer(block);
		return buf.getFloat(offset);
	}
	
	public long getLong(Block block, int offset) {
		concurrencyManager.sharedLock(block);
		Buffer buf = bufferList.getBuffer(block);
		return buf.getLong(offset);
	}
	
	public byte[] getArray(Block block, int offset) {
		concurrencyManager.sharedLock(block);
		Buffer buf = bufferList.getBuffer(block);
		return buf.getArray(offset);
	}

	public int getSize(String filename) {
		Block dummyBlock = new Block(filename, -1);
		concurrencyManager.sharedLock(dummyBlock);
		return Panda.getFileManager().size(filename);
	}

	public void commit() {
		bufferList.unpinAll();
		recoveryManager.commit();
		concurrencyManager.releaseAll();
//		System.out.println("transaction " + transactionNumber + " committed");

	}

	public void rollback() {
		bufferList.unpinAll();
		recoveryManager.rollback();
		concurrencyManager.releaseAll();
//		System.out.println("transaction " + transactionNumber + " rolled back");
	}
	
	public void recover() {
		Panda.getBufferManager().allPageOut(transactionNumber);
		recoveryManager.recover();
	}

	public Block append(String filename, PageFormatter fh) {
		Block dummyBlock = new Block(filename, -1);
		concurrencyManager.exclusiveLock(dummyBlock);
		Block blk = bufferList.pinNew(filename, fh);
		unpin(blk);
		return blk;
	}

	private static synchronized int getTransactionNumber() {
		nextTransactionNumber++;
		return nextTransactionNumber;
	}

	public void unpin(Block blk) {
		bufferList.unpin(blk);
	}

	public void pin(Block blk) {
		bufferList.pin(blk);
	}

}